package com.acme.chat.server;

import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.acme.chat.client.ChatClient;
import com.acme.chat.message.ChatMessage;
import com.acme.chat.message.Message;
import com.acme.chat.message.MessageType;

public class ServerImpl implements Server {
    private static final int AVERAGE_NAME_LENGTH = 25;

    private Set<ChatClient> clients = new HashSet<ChatClient>();
    private Map<String, Set<ChatClient>> clientsInTheRoom = new HashMap<String, Set<ChatClient>>();

    private SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

    public ServerImpl() {

    }

    public synchronized boolean addClient(ChatClient client) {
        if (userExists(client)) {
            String errorMessage = "Another user with this name already on the server!\n" + getAllUserList();
            Message message = new ChatMessage(MessageType.ERROR_USER_EXISTS, errorMessage);
            client.incomingMessage(message);
            return false;
        }

        if (nameIsTooLong(client)) {
            String errorMessage = "Your name is too long!\nMaximum allows is " + ChatClient.MAX_NAME_LENGTH + " symbols.\n";
            Message message = new ChatMessage(MessageType.ERROR_TOO_LONG_NAME, errorMessage);
            client.incomingMessage(message);
            return false;
        }

        if (clients.add(client) && addClientToRoom(client)) {
            Message message = new ChatMessage(MessageType.MESSAGE, "User " + client.getName() + " connected to the room " + client.getRoom() + "\n");
            sendMessageInRoom(client.getRoom(), message);
            return true;
        }
        return false;
    }

    private boolean addClientToRoom(ChatClient client) {
        // add to the map
        Set<ChatClient> clients = getClientsFromRoom(client.getRoom());
        return clients.add(client);
    }

    private Set<ChatClient> getClientsFromRoom(String room) {
        Set<ChatClient> clients = clientsInTheRoom.get(room);
        if (clients == null) {
            clients = new HashSet<ChatClient>();
            clientsInTheRoom.put(room, clients);
        }
        return clients;
    }

    private boolean userExists(ChatClient client) {
        if (clients.contains(client)) {
            return true;
        }
        return false;
    }

    private boolean nameIsTooLong(ChatClient client) {
        return client.getName().length() > ChatClient.MAX_NAME_LENGTH;
    }

    /**
     * Method removes client from the server
     *
     * @param client
     */
    public synchronized boolean removeClient(ChatClient client) {
        sendMessageInRoom(client.getRoom(), new ChatMessage(MessageType.MESSAGE, "Client " + client.getName() + " disconnected from the server!\n"));
        removeClientFromRoom(client);
        return clients.remove(client);
    }

    private void removeClientFromRoom(ChatClient client) {
        // remove from the map
        String room = client.getRoom();
        Set<ChatClient> chatClients = clientsInTheRoom.get(room);
        chatClients.remove(client);
    }

    public synchronized void processMessage(Message message, ChatClient client) {
        switch (message.getType()) {
        case MESSAGE:
            sendCommonMessageFromClient(message, client);
            break;
        case WHOISIN:
            sendListOfUsers(client);
            break;
        default:
            break;
        }
    }

    private void sendCommonMessageFromClient(Message message, ChatClient client) {

        String composeString = getDateFormatted() + ": " + client.getName() + ": " + message.getMessage() + "\n";
        Message composeMessage = new ChatMessage(MessageType.MESSAGE, composeString);

        sendMessageInRoom(client.getRoom(), composeMessage);
    }

    private void sendMessageInRoom(String room, Message message) {
        Set<ChatClient> clients = clientsInTheRoom.get(room);
        for (ChatClient client : clients) {
            client.incomingMessage(message);
        }
    }

//    private void disconnectClient(ChatClient client) {
//        removeClient(client);
//        client.disconnect();
//        // send message to the client to wake up him
//        client.incomingMessage(new ChatMessage(MessageType.MESSAGE, "You are disconnected from the server!"));
//        sendMessageInRoom(client.getRoom(), new ChatMessage(MessageType.MESSAGE, "Client " + client.getName() + " disconnected from the server!\n"));
//    }

    private void sendListOfUsers(ChatClient client) {
        String userList = getUserListFromTheRoom(client.getRoom());
        Message message = new ChatMessage(MessageType.MESSAGE, userList);
        client.incomingMessage(message);
    }

    private String getUserListFromTheRoom(String room) {
        Set<ChatClient> clients = getClientsFromRoom(room);
        StringBuilder sb = new StringBuilder(clients.size() * AVERAGE_NAME_LENGTH);
        sb.append("List of users in the room ").append(room).append(":\n");
        for (ChatClient client : clients) {
            sb.append("< ").append(client.getName()).append(" >\n");
        }
        return sb.toString();
    }

    private String getAllUserList() {
        StringBuilder sb = new StringBuilder(clients.size() * AVERAGE_NAME_LENGTH);
        sb.append("List of users on the server:\n");
        for (ChatClient client : clients) {
            sb.append("< ").append(client.getName()).append(" >\n");
        }
        return sb.toString();
    }

    private String getDateFormatted() {
        return dateFormat.format(new Date());
    }

    public Set<ChatClient> getClients() {
        return Collections.unmodifiableSet(clients);
    }

    public Set<String> getRooms() {
        return Collections.unmodifiableSet(clientsInTheRoom.keySet());
    }

    public Map<String, Set<ChatClient>> getClientsInTheRoom() {
        return Collections.unmodifiableMap(clientsInTheRoom);
    }

}
